// *************************************************************************************************
//
// MK3D Engine
// --------------------------------------
// Copyright (C) 2007-2011 Zeb (mailto: zebbey@gmail.com)
//
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//
// *************************************************************************************************

#include "mk_header.h"
#include "mk_engine.h"
#include "mk_error.h"
#include "mk_d3d9_render_device.h"
#include "mk_camera.h"
#include "mk_render_window.h"
#include "mk_log.h"
#include "mk_painter.h"
#include "mk_resource_manager.h"
#include "mk_scene_manager.h"
#include "mk_timer.h"
#include "mk_file_system.h"
#include "mk_particle_pool.h"
#include "mk_particle_renderer.h"
#include "mk_profiler.h"
#include "mk_post_processor_manager.h"
#include "mk_material.h"
#include "mk_texture.h"

namespace MK3DE
{
	CEngine* __engine = NULL;

	CEngine* GetEngine()
	{
		return __engine;
	}

	BOOL CreateEngine(RENDER_DEVICE_TYPE renderDeviceType, void* mainWndHandle)
	{
		BOOL ret = FALSE;
		SAFE_DELETE(__engine);

		__engine = new CEngine();
		MK_PROCESS_ERROR(__engine);

		ret = __engine->Create(renderDeviceType, mainWndHandle);
		MK_PROCESS_ERROR(ret);

		ret = TRUE;
Exit0:
		return ret;
	}

	void DestroyEngine()
	{
		SAFE_DELETE(__engine);
	}

	CEngine::CEngine()
	{
		_renderDeviceType = RDT_DIRECT3D9;
		_engineLog = NULL;
		_renderDevice = NULL;
		_resourceManager = NULL;
		_sceneManager = NULL;
		_painter = NULL;
		_timer = NULL;
		_fileSystem = NULL;
		_postProcessorManager = NULL;

#if MK_PROFILER_ENABLE
		_profiler = NULL;
#endif

		_particlePool = NULL;
		_particleRenderer = NULL;
		_currentCamera = NULL;
		_currentRenderWindow = NULL;
		_currentShadowMap = NULL;
		_currentReflectionMap = NULL;
		_currentRefractionMap = NULL;
	}

	CEngine::~CEngine()
	{
		SAFE_DELETE(_particleRenderer);
		SAFE_DELETE(_particlePool);
		SAFE_RELEASE(_currentCamera);

#if MK_PROFILER_ENABLE
		SAFE_DELETE(_profiler);
#endif

		SAFE_DELETE(_postProcessorManager);
		SAFE_DELETE(_fileSystem);
		SAFE_DELETE(_timer);
		SAFE_DELETE(_painter);
		SAFE_DELETE(_sceneManager);

		std::vector<CRenderWindow*>::iterator i = _vecRenderWindow.begin();
		while (i != _vecRenderWindow.end())
		{
			CRenderWindow* renderWindow = *i;
			SAFE_DELETE(renderWindow);

			++i;
		}

		_vecRenderWindow.clear();

		SAFE_DELETE(_resourceManager);

		CMaterial::Destroy();

		if (_renderDeviceType == RDT_DIRECT3D9)
		{
			CD3D9RenderDevice* rd = (CD3D9RenderDevice*)_renderDevice;
			SAFE_DELETE(rd);
		}

		SAFE_DELETE(_engineLog);
	}

	BOOL CEngine::Create(RENDER_DEVICE_TYPE renderDeviceType, void* mainWndHandle)
	{
		BOOL ret = FALSE;

		_engineLog = new CLog();
		if (!_engineLog)
			return FALSE;

		_engineLog->Create("mk3de.html", MK3D_ENGINE, MK3D_VERSION);

		_fileSystem = new CFileSystem();
		MK_PROCESS_ERROR(_fileSystem);

		_timer = new CTimer();
		MK_PROCESS_ERROR(_timer);

		ret = _timer->Init();
		MK_PROCESS_ERROR(ret);

		if (renderDeviceType == RDT_DIRECT3D9)
		{
			CD3D9RenderDevice* rd = new CD3D9RenderDevice();
			MK_PROCESS_ERROR(rd);

			ret = rd->Create(mainWndHandle);
			MK_PROCESS_ERROR(ret);

			_renderDevice = rd;
			_engineLog->Write(MKLT_INFO, "Create Direct3D9 render device success.");
		}

		ret = CMaterial::Create();
		MK_PROCESS_ERROR(ret);

		_sceneManager = new CSceneManager();
		MK_PROCESS_ERROR(_sceneManager);

		_resourceManager = new CResourceManager();
		MK_PROCESS_ERROR(_resourceManager);

		ret = _resourceManager->Create();
		MK_PROCESS_ERROR(ret);

		_painter = new CPainter();
		MK_PROCESS_ERROR(_painter);

		ret = _painter->Init(200);
		MK_PROCESS_ERROR(ret);

		_particlePool = new CParticlePool();
		MK_PROCESS_ERROR(ret);

		ret = _particlePool->AllocatePool(2000);
		MK_PROCESS_ERROR(ret);

		_particleRenderer = new CParticleRenderer();
		MK_PROCESS_ERROR(ret);

		_postProcessorManager = new CPostProcessorManager();
		MK_PROCESS_ERROR(_postProcessorManager);

		ret = _postProcessorManager->Create();
		MK_PROCESS_ERROR(ret);

		ret = _particleRenderer->Create(200);
		MK_PROCESS_ERROR(ret);

		_renderDeviceType = renderDeviceType;

#if MK_PROFILER_ENABLE
		_profiler = new CProfiler();
		MK_PROCESS_ERROR(_profiler);

		ret = _profiler->Init();
		MK_PROCESS_ERROR(ret);
#endif

		ret = TRUE;
Exit0:
		return ret;
	}

	CRenderWindow* CEngine::CreateRenderWindow(void* wndHandle)
	{
		CRenderWindow* renderWindow = GetRenderWindow(wndHandle);
		if (renderWindow)
			return renderWindow;

		renderWindow = new CRenderWindow();
		MK_PROCESS_ERROR(renderWindow);

		renderWindow->SetWindowHandle(wndHandle);
		_vecRenderWindow.push_back(renderWindow);

Exit0:
		return renderWindow;
	}

	BOOL CEngine::__GetRenderWindow(void* wndHandle, std::vector<CRenderWindow*>::iterator& i)
	{
		i = _vecRenderWindow.begin();
		while (i != _vecRenderWindow.end())
		{
			CRenderWindow* renderWindow = *i;
			if (wndHandle == renderWindow->GetWindowHandle())
				return TRUE;

			++i;
		}

		return FALSE;
	}

	CRenderWindow* CEngine::GetRenderWindow(void* wndHandle)
	{
		std::vector<CRenderWindow*>::iterator i;
		if (__GetRenderWindow(wndHandle, i))
			return *i;

		return NULL;
	}

	BOOL CEngine::DestroyRenderWindow(void* wndHandle)
	{
		std::vector<CRenderWindow*>::iterator i;
		if (__GetRenderWindow(wndHandle, i))
		{
			_vecRenderWindow.erase(i);
			return TRUE;
		}

		return FALSE;
	}

	void CEngine::OnRenderWindowResize(void* wndHandle)
	{
		CRenderWindow* renderWindow = GetRenderWindow(wndHandle);
		if (!renderWindow)
			return;

		renderWindow->OnResized();
		__RecalculateCameraAspectRatio();
		_renderDevice->OnResize(renderWindow);

		if (_vecRenderWindow.size() && _vecRenderWindow[0] == renderWindow)
			_postProcessorManager->OnDeviceLost();
	}

	void CEngine::Update()
	{
		static DWORD lastUpdate = _timer->GetMilliseconds();
		DWORD now = _timer->GetMilliseconds();
		if (now < lastUpdate)
		{
			lastUpdate = now;
			return;
		}

		DWORD dt = now - lastUpdate;
		if (dt == 0)
			return;

		_sceneManager->Update(dt);
		_currentCamera->Update(dt);
		lastUpdate = now;
	}

	void CEngine::Render()
	{
		BOOL bEnablePostProcess = _postProcessorManager->IsEnable();
		std::vector<CRenderWindow*>::iterator i = _vecRenderWindow.begin();
		while (i != _vecRenderWindow.end())
		{
			if (i != _vecRenderWindow.begin())
				_postProcessorManager->Enable(FALSE);
			else
				_postProcessorManager->Enable(bEnablePostProcess);

			CRenderWindow* renderWindow = *i;
			if (renderWindow->IsEnable())
			{
				SetCurrentRenderWindow(renderWindow);
				renderWindow->Render();
			}

			++i;
		}
	}

	void CEngine::SetCurrentCamera(CCamera* camera)
	{
		_currentCamera = camera;
		_currentCamera->AddRef();
		__RecalculateCameraAspectRatio();
	}

	void CEngine::SetCurrentRenderWindow(CRenderWindow* renderWindow)
	{
		_currentRenderWindow = renderWindow;
		__RecalculateCameraAspectRatio();
	}

	void CEngine::__RecalculateCameraAspectRatio()
	{
		if (_currentRenderWindow && _currentCamera)
		{
			float width = (float)_currentRenderWindow->GetWidth();
			float height = (float)_currentRenderWindow->GetHeight();
			float aspect = width / height;
			_currentCamera->SetWidth(width);
			_currentCamera->SetHeight(height);
			_currentCamera->SetAspectRatio(aspect);
		}
	}
}